<?php
class Majix {
	const C_MYSQL = "mysql";
	const C_HOST = "host";
	const C_USERNAME = "username";
	const C_PASSWD = "passwd";
	const C_DBNAME = "dbname";
	const C_MAJIX = "majix";
	const C_PREPEND = "prepend";
	const ID = "id";
	const ID_FOR_INJECTION = -42;

	public static $con = array();
	public static $config = array();
	public static $config_layout = array(
		Majix::C_MYSQL => array(
			Majix::C_HOST,
			Majix::C_USERNAME,
			Majix::C_PASSWD,
			Majix::C_DBNAME,
		),
		Majix::C_MAJIX => array(
			Majix::C_PREPEND,
		)
	);

	private $class;
	private $lclass;
	private $values = null;
	private $new_values = null;

   public function __construct($id = null, $ini_file_or_array = null) {
   	$this->class = get_class($this);
   	$this->lclass = strtolower($this->class);

   	if ($this->getConfiguration() === null) {
   		$this->setConfiguration($ini_file_or_array);
		}

		if ($id == Majix::ID_FOR_INJECTION) {
			//Do nothing...
		} else if ($id != null) {
			$this->values = array(
				Majix::ID => $id
			);
		} else {
			$this->insert();
		}
   }

   public function __destruct() {
   	$this->update();
	}

   public function __call($funcname, $params) {
   	$matches = array();

   	if (preg_match('/^get(.+)$/', $funcname, $matches)) {
   		$get = strtolower($matches[1]);
   		return $this->get($get);
		}

   	if (preg_match('/^set(.+)$/', $funcname, $matches)) {
   		$set = strtolower($matches[1]);
   		return $this->set($set, $params[0]);
		}

      throw new MajixException($this->class . "::" . $funcname . " is not supported.");
   }

   public function inject($row) {
   	if ($this->values != null) {
   		$this->values = array_merge($row, $this->values);
		} else {
			$this->values = $row;
		}
	}

   private function get($var) {
   	if (isset($this->values[$var])) {
   		return $this->values[$var];
		} else if ($var == Majix::ID) {
			throw new MajixException("$var is not set. Can this happen?");
		}

		$this->select();

		return isset($this->values[$var]) ? $this->values[$var] : null;
	}

	private function set($var, $val) {
		if ($var == Majix::ID) {
			throw new MajixException("Cannot assign a value to $var.");
		}

		$this->values[$var] = $val;

		if ($this->new_values === null) {
			$this->new_values = array();
		}

		$this->new_values[$var] = $val;
	}

	private function select() {
		$c = $this->getConfiguration();

		$this->connect();

		$getId = "get".Majix::ID;
		$r = mysqli_query(Majix::$con[$this->class], "SELECT * FROM `{$c[Majix::C_MAJIX][Majix::C_PREPEND]}{$this->lclass}` WHERE `".Majix::ID."` = {$this->$getId()};");
		$row = mysqli_fetch_assoc($r);

		if ($row != null) {
			$this->values = array_merge($row, $this->values);
		} else {
			throw new MajixException("Attempting to select a {$this->class} with ".Majix::ID."={$this->$getId()}, but nothing was found.");
		}
	}

	private function insert() {
		$c = $this->getConfiguration();

		$this->connect();

		if (mysqli_query(Majix::$con[$this->class], "INSERT INTO `{$c[Majix::C_MAJIX][Majix::C_PREPEND]}{$this->lclass}` VALUES ();")) {

			$this->values[Majix::ID] = mysqli_insert_id(Majix::$con[$this->class]);
		} else {
			$this->create();

			mysqli_query(Majix::$con[$this->class], "INSERT INTO `{$c[Majix::C_MAJIX][Majix::C_PREPEND]}{$this->lclass}` VALUES ();");
			$this->values[Majix::ID] = mysqli_insert_id(Majix::$con[$this->class]);
		}
	}

	private function update() {
		if ($this->new_values === null) {
			return;
		}

		$c = $this->getConfiguration();

		$this->connect();

		$assigns = array();

		foreach(array_keys($this->new_values) as $col) {
			$assigns[] = "`$col` = '{$this->values[$col]}'";
		}

		if (count($assigns) > 0) {
			$sql = "UPDATE `{$c[Majix::C_MAJIX][Majix::C_PREPEND]}{$this->lclass}` SET ".implode(", ", $assigns)." WHERE `".Majix::ID."` = {$this->getId()};";

			if (!mysqli_query(Majix::$con[$this->class], $sql)) {
				$this->alter();
				mysqli_query(Majix::$con[$this->class], $sql);
			}
		}
	}

	private function create() {
		$c = $this->getConfiguration();

		if (!mysqli_query(Majix::$con[$this->class], "CREATE TABLE `{$c[Majix::C_MAJIX][Majix::C_PREPEND]}{$this->lclass}` ( `".Majix::ID."` INT(11) NOT NULL AUTO_INCREMENT, PRIMARY KEY (`".Majix::ID."`));")) {
			throw new MajixException("MySQL error while attempting to create table for {$this->class}: " . mysqli_error(Majix::$con[$this->class]));
		}
	}

	private function alter() {
		$c = $this->getConfiguration();

		$sqls = array();
		foreach ($this->values as $col => $val) {
			switch(true) {
				case is_string($val):
					$type = "TEXT";
					break;
				case is_float($val):
					$type = "FLOAT";
					break;
				case is_int($val):
					$type = "INT";
					break;
				default:
					die("DIE: " . $this->class . ": Unsupported type, " . gettype($val) . ", for property $col.");
			}

			$sql = "ALTER TABLE `{$c[Majix::C_MAJIX][Majix::C_PREPEND]}{$this->lclass}` ADD `$col` $type NOT NULL;";
			$sqls[] = $sql;

			foreach($sqls as $sql) {
				mysqli_query(Majix::$con[$this->class], $sql);
			}
		}
	}

	private function connect() {
		$c = $this->getConfiguration();
		
		$con_id = $c[Majix::C_MYSQL][Majix::C_HOST] . "//" . $c[Majix::C_MYSQL][Majix::C_DBNAME] . "//" . $c[Majix::C_MYSQL][Majix::C_PREPEND];

		if (isset(Majix::$con[$con_id])) {
			return;
		}

		if ((Majix::$con[$con_id] = mysqli_connect($c[Majix::C_MYSQL][Majix::C_HOST], $c[Majix::C_MYSQL][Majix::C_USERNAME], $c[Majix::C_MYSQL][Majix::C_PASSWD], $c[Majix::C_MYSQL][Majix::C_DBNAME])) === FALSE) {
			throw new MajixException("Could not connect to MySQL database ({$this->class}): " . mysqli_connect_error());
		}
	}

   private function getConfiguration() {
   	return isset(Majix::$config[$this->class]) ? Majix::$config[$this->class] : null;
	}

	private function setConfiguration($ini_file_or_array) {
		if ($ini_file_or_array === null) {
			$ini_file_or_array = _MAJIX_INI_FILE;
		}

		if (is_array($ini_file_or_array)) {
			Majix::$config[$this->class] = $ini_file_or_array;
		} else if (is_string($ini_file_or_array)) {
			Majix::$config[$this->class] = parse_ini_file($ini_file_or_array, true);
		} else {
			throw new MajixException("Could not configure Majix class, " . $this->class . ".");
		}
		foreach(Majix::$config_layout as $section => $section_arr) {
			if (!isset(Majix::$config[$this->class][$section])) {
				throw new MajixException("Configuration section, $section, is missing.");
			}
			foreach ($section_arr as $setting) {
				if (!isset(Majix::$config[$this->class][$section][$setting])) {
					throw new MajixException("Configuration setting, [$section]:$setting, is missing.");
				}
			}
		}
	}
}

class MajixException extends Exception {
}
